home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / launchpad-integration / launchpadintegration / packageinfo.py < prev    next >
Text File  |  2009-09-24  |  11KB  |  294 lines

  1. """
  2. Code used to identify a package based on one of the following:
  3.  * A process ID
  4.  * A file name
  5.  * A desktop file
  6.  * A binary package name
  7. """
  8.  
  9. import sys
  10. import os
  11. import socket
  12.  
  13. import subprocess
  14.  
  15. DPKGDIR = '/var/lib/dpkg'
  16. INFODIR = os.path.join(DPKGDIR, 'info')
  17. STATUSFILE = os.path.join(DPKGDIR, 'status')
  18.  
  19. class PackageNotFoundError(Exception):
  20.     pass
  21.  
  22.  
  23. def _get_pkg(filename):
  24.     """Find the binary package associated with the given filename
  25.  
  26.     This scans the list files in /var/lib/dpkg/info, since it is faster
  27.     than 'dpkg-query -S'.
  28.     """
  29.     for listfile in os.listdir(INFODIR):
  30.         if not listfile.endswith('.list'): continue
  31.         contents = open(os.path.join(INFODIR, listfile), 'r').read()
  32.         if filename in contents.splitlines(False):
  33.             return listfile[:-len('.list')]
  34.  
  35.  
  36. class PackageInfo(object):
  37.     def __init__(self, binarypackage, sourcepackage, provides,
  38.                  version, architecture, status, dependencies):
  39.         self.binarypackage = binarypackage
  40.         self.sourcepackage = sourcepackage or binarypackage
  41.         self.provides = set(provides)
  42.         self.version = version
  43.         self.architecture = architecture
  44.         self.status = status
  45.         self.dependencies = set(dependencies)
  46.  
  47.         self.names = set(self.provides)
  48.         self.names.add(self.binarypackage)
  49.  
  50.     @property
  51.     def installed(self):
  52.         if self.status:
  53.             state = self.status.split()[2]
  54.             return state not in ('config-files', 'not-installed')
  55.         else:
  56.             return False
  57.  
  58.     @property
  59.     def shortstatus(self):
  60.         if self.status:
  61.             sinfo = self.status.split()
  62.             return sinfo[0][0] + sinfo[2][0]
  63.         else:
  64.             return None
  65.  
  66.     def __repr__(self):
  67.         return "<PackageInfo '%s_%s_%s'>" % (self.binarypackage,
  68.                                              self.version,
  69.                                              self.architecture)
  70.  
  71.     @classmethod
  72.     def fromXID(cls, xid=None, logger=None):
  73.         """Return a PackageInfo instance corresponding to a window
  74.  
  75.         This is performed by calling xprop to get WM_CLIENT_MACHINE
  76.         and _NET_WM_PID properties of a window.  If WM_CLIENT_MACHINE
  77.         is our hostname, and _NET_WM_PID is set, then chain to
  78.         fromProcessID().
  79.  
  80.         If no XID is passed, xprop works in 'picker' mode.
  81.         """
  82.         cmdline = ['xprop', '-notype']
  83.         if xid is not None:
  84.             cmdline.extend(['-id', str(xid)])
  85.             
  86.         cmdline.extend(['WM_CLIENT_MACHINE', '_NET_WM_PID'])
  87.         if logger:
  88.             logger.debug('Executing %r', cmdline)
  89.         try:
  90.             p = subprocess.Popen(cmdline,
  91.                                  stdout=subprocess.PIPE,
  92.                                  stderr=subprocess.PIPE,
  93.                                  close_fds=True)
  94.             stdout, stderr = p.communicate()
  95.             result = p.returncode
  96.         except (OSError, IOError):
  97.             if logger:
  98.                 logger.exception('Could not find process ID')
  99.             raise PackageNotFoundError('Could not find process ID')
  100.         if result != 0:
  101.             raise PackageNotFoundError('Could not find process ID')
  102.  
  103.         # unpack properties
  104.         props = dict(x.split(' = ', 1) for x in stdout.splitlines(False))
  105.  
  106.         if logger:
  107.             logger.debug('WM_CLIENT_MACHINE = %s, _NET_WM_PID = %s',
  108.                          props.get('WM_CLIENT_MACHINE'),
  109.                          props.get('_NET_WM_PID'))
  110.  
  111.         # if the window comes from another host, or doesn't have a PID
  112.         # property, raise an exception.
  113.         hostname = '"%s"' % socket.gethostname()
  114.         if (props.get('WM_CLIENT_MACHINE', hostname) != hostname or
  115.             '_NET_WM_PID' not in props):
  116.             raise PackageNotFoundError('Could not find process ID')
  117.  
  118.         try:
  119.             pid = int(props['_NET_WM_PID'])
  120.         except ValueError:
  121.             raise PackageNotFoundError('Could not find process ID')
  122.  
  123.         if logger:
  124.             logger.info('Process ID for selected window is %d', pid)
  125.  
  126.         return cls.fromProcessID(pid, logger)
  127.  
  128.     @classmethod
  129.     def fromProcessID(cls, pid, logger=None):
  130.         """Return a PackageInfo instance corresponding to a process ID
  131.  
  132.         This is performed by looking up the executable name in the /proc
  133.         filesystem, then chaining to fromFilename().
  134.         """
  135.         if logger:
  136.             logger.debug('Looking up executable for process %d', pid)
  137.         try:
  138.             filename = os.readlink('/proc/%d/exe' % pid)
  139.         except OSError:
  140.             if logger:
  141.                 logger.exception('Could not find executable for process %d',
  142.                                  pid)
  143.             raise PackageNotFoundError('Could not find executable for '
  144.                                        'process %d' % pid)
  145.  
  146.         # if the executable is deleted, then the user is not running the
  147.         # version of the file installed by the package (they probably
  148.         # started the app, then ran "apt-get upgrade" or similar).
  149.         if filename.endswith(' (deleted)'):
  150.             logger.error('Process %d is running deleted executable "%s"',
  151.                          pid, filename)
  152.             raise PackageNotFoundError('Process %d is running deleted '
  153.                                        'executable "%s"' %(pid, filename))
  154.         if logger:
  155.             logger.info('Executable for process %d is "%s"', pid, filename)
  156.         # dirty fix for the Live CD
  157.         if filename.startswith('/rofs'):
  158.             filename = filename[len('/rofs'):]
  159.         elif filename.startswith('/filesystem.squashfs'):
  160.             filename = filename[len('/filesystem.squashfs'):]
  161.     return cls.fromFilename(filename, logger)
  162.  
  163.     @classmethod
  164.     def fromDesktopFile(cls, filename, logger=None):
  165.         """Return a PackageInfo instance corresponding to a Desktop file
  166.  
  167.         This is performed by looking for the executable name in the 'exec'
  168.         line of the desktop file, then chains to fromFilename().
  169.  
  170.         This function should be used instead of fromFilename() because
  171.         the desktop file might be a customised one in the user's home
  172.         directory, but still points to an installed application.
  173.         """
  174.         raise NotImplementedError
  175.  
  176.     @classmethod
  177.     def fromFilename(cls, filename, logger=None):
  178.         """Return a PackageInfo instance corresponding to a file
  179.  
  180.         This is performed by finding the package that owns the file
  181.         using dpkg-query, and then chaining to fromPackageName to
  182.         fill in the PackageInfo instance.
  183.         """
  184.         if logger:
  185.             logger.debug('Looking up binary package name for file "%s"',
  186.                          filename)
  187.         try:
  188.             package = _get_pkg(filename)
  189.         except (OSError, IOError):
  190.             if logger:
  191.                 logger.exception('Could not find binary package for file "%s"',
  192.                                  filename)
  193.             raise PackageNotFoundError('Could not find binary package for '
  194.                                        'file "%s"' % filename)
  195.         if package is None:
  196.             raise PackageNotFoundError('Could not look up binary package for '
  197.                                        'file "%s"' % filename)
  198.         if logger:
  199.             logger.info('Binary package for file "%s" is "%s"',
  200.                          filename, package)
  201.  
  202.         return cls.fromPackageName(package, logger)
  203.  
  204.     @classmethod
  205.     def fromPackageName(cls, package, logger=None):
  206.         """Return a PackageInfo instance for a particular binary package name
  207.         """
  208.         if logger:
  209.             logger.debug('Looking up package information for "%s"', package)
  210.  
  211.         for info in cls._iterPackages():
  212.             if package in info.names:
  213.                 break
  214.         else:
  215.             raise PackageNotFoundError('Could not look up package info for '
  216.                                        '"%s"' % package)
  217.  
  218.         if logger:
  219.             logger.debug('Package info for %s is %r', package, info)
  220.         return info
  221.  
  222.     @classmethod
  223.     def fromPackageNames(cls, packages, logger=None):
  224.         """Iterate through PackageInfo instances that match names in packages.
  225.  
  226.         This is an optimisation of fromPackageName() for when you want
  227.         multiple packages.
  228.         """
  229.         packages = set(packages)
  230.         for info in cls._iterPackages():
  231.             if packages & info.names:
  232.                 if logger:
  233.                     logger.debug('Package %r matched', info)
  234.                 yield info
  235.  
  236.     @classmethod
  237.     def _iterPackages(cls, filename=STATUSFILE):
  238.         """Iterate through the list of packages in the DPKG database"""
  239.         package = None
  240.         status = None
  241.         architecture = None
  242.         source = None
  243.         version = None
  244.         provides = []
  245.         dependencies = []
  246.         for line in open(filename, 'r'):
  247.             if line == '\n': # end of record
  248.                 yield cls(binarypackage=package,
  249.                           sourcepackage=source,
  250.                           provides=provides,
  251.                           version=version,
  252.                           architecture=architecture,
  253.                           status=status,
  254.                           dependencies=dependencies)
  255.                 package = None
  256.                 status = None
  257.                 architecture = None
  258.                 source = None
  259.                 version = None
  260.                 provides = []
  261.                 dependencies = []
  262.             elif line.startswith('Package: '):
  263.                 package = line[len('Package: '):].strip()
  264.             elif line.startswith('Status: '):
  265.                 status = line[len('Status: '):].strip()
  266.             elif line.startswith('Architecture: '):
  267.                 architecture = line[len('Architecture: '):].strip()
  268.             elif line.startswith('Source: '):
  269.                 source = line[len('Source: '):].strip()
  270.             elif line.startswith('Version: '):
  271.                 version = line[len('Version: '):].strip()
  272.             elif line.startswith('Provides: '):
  273.                 provides.extend(x.strip()
  274.                                 for x in line[len('Provides: '):].split(', '))
  275.             elif line.startswith('Depends: '):
  276.                 deps = line[len('Depends: '):].strip()
  277.                 dependencies.extend(y.split()[0]
  278.                                     for x in deps.split(', ')
  279.                                     for y in x.split('|'))
  280.             elif line.startswith('Pre-Depends'):
  281.                 deps = line[len('Pre-Depends: '):].strip()
  282.                 dependencies.extend(y.split()[0]
  283.                                     for x in deps.split(', ')
  284.                                     for y in x.split('|'))
  285.         if package is not None:
  286.             yield cls(binarypackage=package,
  287.                       sourcepackage=source,
  288.                       provides=provides,
  289.                       version=version,
  290.                       architecture=architecture,
  291.                       status=status,
  292.                       dependencies=dependencies)
  293.  
  294.